#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

from typing import Optional

from common.const import ExecuteResultEnum
from common.const import ReportDBLabel
from common.const import RpcToolInterface
from common.schemas.thrift_base_data_type import (ActionResult, SubJob, ExecutePolicy, SubJobType)
from common.schemas.thrift_framework import SubJobStatus, SubJobDetails, LogDetail, DBLogLevel
from common.schemas.thrift_plugin import RestoreJob, JobResult
from saporacle.comm.common_util import rpctool_api, logger_watched
from saporacle.comm.constants import SAPOracleErrorCode, SAPOracleJobLabel, SAPOracleFile
from saporacle.comm.logger import logger
from saporacle.comm.saporacle_exception import SAPOracleException
from saporacle.comm.datamodels import BackintParam
from saporacle.comm.repository import Repository
from saporacle.restore.restore_saporacle import RestoreSAPOracle


class SAPOracleRestoreService:

    def __init__(self, pid: str, saporacle: RestoreSAPOracle, repository: Optional[Repository]) -> None:
        self._pid = pid
        self._saporacle = saporacle
        self._repository = repository

    def get_source_oracle_sid(self):
        return self._saporacle.source_oracle_sid

    def get_oracle_sid(self):
        return self._saporacle.oracle_sid

    def allow_restore_in_local_node(self, restore_job: RestoreJob) -> ActionResult:
        try:
            self._saporacle.login()
            logger.error("Not allow restore in local node. Database is open.")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR, bodyErr=SAPOracleErrorCode.DATABASE_ONLINE)
        except SAPOracleException as e:
            if e.err_code == SAPOracleErrorCode.DATABASE_OFFLINE:
                logger.info("Allow restore in local node. Database is offline.")
                return ActionResult(code=ExecuteResultEnum.SUCCESS)
            logger.error(f"Not allow restore in local node. {e}")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR, bodyErr=e.err_code, bodyErrParams=e.parameters)

    def restore_prerequisite(self, restore_job: RestoreJob) -> None:
        try:
            self._saporacle.all_brtools_exist()
            not self._saporacle.is_backint_installed() and self._saporacle.install_backint()
        except SAPOracleException as e:
            logger.error(f"Restore prerequisite job failed. {e}")
            job_details = SubJobDetails(taskId=restore_job.job_id, taskStatus=SubJobStatus.FAILED)
            rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))
            return
        logger.info("Restore prerequisite job succeeded.")
        job_details = SubJobDetails(taskId=restore_job.job_id, taskStatus=SubJobStatus.COMPLETED)
        rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))

    def restore_gen_sub_job(self, restore_job: RestoreJob) -> list[SubJob]:
        sub_jobs = [
            SubJob(jobId=restore_job.job_id, jobType=SubJobType.BUSINESS_SUB_JOB, jobName=self.prepare_sub_job.__name__,
                   policy=ExecutePolicy.ANY_NODE, jobPriority=1),
            SubJob(jobId=restore_job.job_id, jobType=SubJobType.BUSINESS_SUB_JOB, jobName=self.restore_sub_job.__name__,
                   policy=ExecutePolicy.ANY_NODE, jobPriority=2)
        ]
        return sub_jobs

    def restore(self, restore_job: RestoreJob, sub_job: SubJob) -> None:
        getattr(self, sub_job.job_name)(restore_job, sub_job)

    def restore_post_job(self, job_result: JobResult, restore_job: RestoreJob, sub_job: SubJob) -> None:
        try:
            self._saporacle.dump_brtools_outfile()
            self._saporacle.remove_backint_param_file()
            self._saporacle.remove_restore_data_path()
            self._repository.remove_cache_dir()
        except SAPOracleException as e:
            logger.error(f"Restore post job failed. {e}")
            job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                        taskStatus=SubJobStatus.FAILED_NOTRY)
            rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))
            return
        logger.info("Restore post job succeeded.")
        job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                    taskStatus=SubJobStatus.COMPLETED)
        rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))

    def pause_job(self, restore_job: RestoreJob, sub_job: SubJob) -> ActionResult:
        try:
            self._saporacle.terminate_brrestore()
        except SAPOracleException as e:
            logger.error(f"Pause restore failed. {e}")
            return ActionResult(code=ExecuteResultEnum.INTERNAL_ERROR, bodyErr=e.err_code, bodyErrParams=e.parameters)
        logger.info("Pause restore succeeded.")
        return ActionResult(code=ExecuteResultEnum.SUCCESS)

    """自定义恢复子任务"""

    @logger_watched(2)
    def prepare_sub_job(self, restore_job: RestoreJob, sub_job: SubJob) -> None:
        # 待解决： 通道数字段不一致
        channel = min(int(restore_job.extend_info.get("CHANNELS")), self._saporacle.cpu_count()) \
            if restore_job.extend_info.get("CHANNELS") else 1
        logger.info(f"Restore job is running. Restore channel_number is {restore_job.extend_info.get('CHANNELS')}, "
                    f"cpu_count is {self._saporacle.cpu_count()}, channel is {channel}.")
        backint_param = BackintParam(pid=self._pid, data_path=self._saporacle.restore_data_path(), parallel=channel)
        self._saporacle.write_backint_param_file(backint_param.dict(by_alias=True))
        try:
            logger.info("Restore job is running, start to redirect_data_paths.")
            self._repository.redirect_data_paths(self._saporacle.restore_data_path())
            logger.info("Restore job is running, start to remove_temp_data_file.")
            self._saporacle.remove_temp_data_file()
            logger.info("Restore job is running, start to _restore_non_data.")
            self._restore_non_data(bool(restore_job.extend_info.get("restoreTimestamp")))
        except SAPOracleException as e:
            logger.error(f"Prepare sub job failed. {e}")
            log_detail = LogDetail(logInfo=ReportDBLabel.SUB_JOB_FALIED, logInfoParam=[sub_job.sub_job_id],
                                   logDetail=e.err_code, logDetailParam=e.parameters, logLevel=DBLogLevel.ERROR)
            job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                        taskStatus=SubJobStatus.FAILED, logDetail=[log_detail])
            rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))
            return
        logger.info("Prepare sub job succeeded.")
        log_detail = LogDetail(logInfo=ReportDBLabel.SUB_JOB_SUCCESS, logInfoParam=[sub_job.sub_job_id],
                               logLevel=DBLogLevel.INFO)
        job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                    taskStatus=SubJobStatus.COMPLETED, logDetail=[log_detail])
        rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))

    @logger_watched(2)
    def restore_sub_job(self, restore_job: RestoreJob, sub_job: SubJob) -> None:
        timestamp = restore_job.extend_info.get("restoreTimestamp")
        # 下发的 timestamp 不是 int 类型
        args = (self._repository.datafiles(SAPOracleFile.BRBACKUP_DETAIL_LOG)[0],
                self._saporacle.timestamp_to_date(int(timestamp)) if timestamp else None)
        try:
            for trans, total in self._saporacle.run_brrecover(*args):
                logger.info(f"Recover running. {trans}/{total}")
                log_detail = LogDetail(logInfo=SAPOracleJobLabel.RESTORE_RUNNING_LABEL, logLevel=DBLogLevel.INFO,
                                       logInfoParam=[self._saporacle.host_name(), f"{total:.2f} MB", f"{trans:.2f} MB"])
                job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                            taskStatus=SubJobStatus.RUNNING, logDetail=[log_detail])
                rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))
        except SAPOracleException as e:
            logger.error(f"Recover failed. {e}")
            log_detail = LogDetail(logInfo=ReportDBLabel.SUB_JOB_FALIED, logInfoParam=[sub_job.sub_job_id],
                                   logDetail=e.err_code, logDetailParam=e.parameters, logLevel=DBLogLevel.ERROR)
            job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                        taskStatus=SubJobStatus.FAILED, logDetail=[log_detail])
            rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))
            return
        logger.info("Recover succeeded.")
        log_detail = LogDetail(logInfo=ReportDBLabel.SUB_JOB_SUCCESS, logInfoParam=[sub_job.sub_job_id],
                               logLevel=DBLogLevel.INFO)
        job_details = SubJobDetails(taskId=restore_job.job_id, subTaskId=sub_job.sub_job_id,
                                    taskStatus=SubJobStatus.COMPLETED, logDetail=[log_detail])
        rpctool_api(self._pid, RpcToolInterface.REPORT_JOB_DETAIL, job_details.dict(by_alias=True))

    """private"""

    def _restore_non_data(self, is_log_restore: bool) -> None:
        """
        :raise SAPOracleException:
        """
        # 恢复目标位置的oracle_sid与副本内的oracle_sid不一致场景
        # back<SOURCE_SID>.log文件恢复后需要改文件名称为back<DESTINATION_SID>.log
        # arch<SOURCE_SID>.log文件恢复后需要改文件名称为arch<DESTINATION_SID>.log
        # initSID.sap/initSID.ora文件恢复后需要改文件名称为目标环境的SID<DESTINATION_SID>
        # initSID.sap/initSID.ora文件恢复后需要修改文件内容，将 <SOURCE_SID>参数更改为 <DESTINATION_SID>
        # 其余文件正常恢复，不需要改文件名称，不需要修改内容
        if self.get_source_oracle_sid() != self.get_oracle_sid():
            logger.info(
                f"Restore job _source_oracle_sid = {self.get_source_oracle_sid()}, "
                f"_oracle_sid = {self.get_oracle_sid()}")
            # initSID.sap/initSID.ora文件恢复后需要改文件名称为目标环境的SID<DESTINATION_SID>
            # initSID.sap/initSID.ora文件恢复后需要修改文件内容，将 <SOURCE_SID>参数更改为 <DESTINATION_SID>
            self._repository.download_datafiles(SAPOracleFile.SAP_PROFILE, self._saporacle.database_sap_profile())
            self._repository.replace_datafiles(self.get_source_oracle_sid(), self.get_oracle_sid(),
                                               self._saporacle.database_sap_profile())
            # PFILE恢复后需要修改文件名/修改内容，SPFILE不需要恢复，直接用目标位置的
            self._repository.download_datafiles(SAPOracleFile.ORACLE_PROFILE, self._saporacle.database_ora_profile())
            self._repository.replace_datafiles(self.get_source_oracle_sid(), self.get_oracle_sid(),
                                               self._saporacle.database_ora_profile())
            # back<SOURCE_SID>.log文件恢复后需要改文件名称为back<DESTINATION_SID>.log
            self._repository.download_datafiles(SAPOracleFile.BRBACKUP_SUMMARY_LOG,
                                                self._saporacle.sapbackup_summary_log_file())
            # 修改文件名
            self._repository.download_datafiles(SAPOracleFile.SPACE_LOG, self._saporacle.sapceorg_summary_log_file())
            # ctrl文件正常恢复不需要改名字
            _dirs = self._saporacle.control_file_dirs()
            [self._repository.download_datafiles(SAPOracleFile.CONTROL_FILE, d) for d in _dirs]
            # 明细文件正常恢复不需要改名字
            self._repository.download_datafiles(SAPOracleFile.BRBACKUP_DETAIL_LOG, self._saporacle.sapbackup_dir())
            if is_log_restore:
                # arch<SOURCE_SID>.log文件恢复后需要改文件名称为arch<DESTINATION_SID>.log
                self._repository.download_datafiles(SAPOracleFile.BRARCHIVE_SUMMARY_LOG,
                                                    self._saporacle.saparch_summary_log_file())
                # 明细文件正常恢复不需要改名字
                self._repository.download_datafiles(SAPOracleFile.BRARCHIVE_DETAIL_LOG, self._saporacle.saparch_dir())
        else:
            logger.info(
                f"Restore job _source_oracle_sid = {self.get_source_oracle_sid()}, "
                f"_oracle_sid = {self.get_oracle_sid()}")
            _dirs = self._saporacle.control_file_dirs()
            [self._repository.download_datafiles(SAPOracleFile.CONTROL_FILE, d) for d in _dirs]
            self._repository.download_datafiles(SAPOracleFile.BRBACKUP_SUMMARY_LOG, self._saporacle.sapbackup_dir())
            self._repository.download_datafiles(SAPOracleFile.BRBACKUP_DETAIL_LOG, self._saporacle.sapbackup_dir())
            profiles = [SAPOracleFile.SAP_PROFILE, SAPOracleFile.ORACLE_PROFILE, SAPOracleFile.SPFILE]
            [self._repository.download_datafiles(f, self._saporacle.database_dir()) for f in profiles]
            self._repository.download_datafiles(SAPOracleFile.SPACE_LOG, self._saporacle.sapceorg_dir())
            if is_log_restore:
                self._repository.download_datafiles(SAPOracleFile.BRARCHIVE_SUMMARY_LOG, self._saporacle.saparch_dir())
                self._repository.download_datafiles(SAPOracleFile.BRARCHIVE_DETAIL_LOG, self._saporacle.saparch_dir())
